#include "Conexion.h"
#include "Servidor.h"
#include <string>
#include "Log.h"

Conexion::Conexion(Socket *socket, Servidor *servidor) {
    this->socket = socket;
    this->servidor = servidor;
    this->conectado = true;
    this->usuario = NULL;
    this->estado = CX_ACTIVA;
}

Conexion::~Conexion() {
    delete socket;
    if (this->autenticada())
        delete usuario;
}

void Conexion::finalizar() {
    this->estado = CX_FINALIZADA;
}

bool Conexion::finalizada() {
    return this->estado == CX_FINALIZADA;
}

void *Conexion::run() {
    static char mensaje_serial[TAMANIO_MAXIMO_MENSAJE];
    Mensaje msg_recibido, msg_envio;
    std::string respuesta;
    Json::Value contenido;
    Json::Value info_niveles_permitidos;
    int id, auth_error, cant_jugadores, nivel;
    std::stringstream ss;

    bool responder_req;
    int error = 0;
    while (estaConectada() && !finalizada()) {
        bool mensaje_de_sesion = false;
        bool mensaje_corrupto = false;

        msg_envio.limpiar();
        msg_recibido.limpiar();
        contenido.clear();

        msg_envio.data["respuesta"] = "";
        msg_envio.data["contenido"] = "";

        responder_req = true;

        if ((error = socket->recibirConAvisoDeLong(mensaje_serial,
                     TAMANIO_MAXIMO_MENSAJE)) <= 0) {
            Log::loguear(MWARN, "Error en recepcion de conexion!");
            /* Evito entrar en bucle infinito por usuario desconectado
               inesperadamente */
            this->desconectar();
            responder_req = false;
            if (error == -3)
                Log::loguear(MERROR, "Error de buffer!");
        } else {
            /* Recibe mensaje serializado */
            msg_recibido.hidratar(mensaje_serial);
            int operacion = msg_recibido.data["req_type"].asInt();


            /* Imprime por pantalla el request recibido */
            if (!msg_recibido.data["req_type"].isNull()){
                ss.str("");
                ss << "Recibiendo peticion: " 
                   << Protocolo::reqToString(operacion)
                   << std::endl;
                   if (IMPRIMIR_PETICIONES_CLIENTE == 1)
                        ss << msg_recibido.aString() << std::endl;
                Log::loguear(MINFO, ss.str());
            }

           
            /** Mensajes validos para cualquier conexion no autenticada:
                    No es necesario firmar los mensajes ni estar autenticado.*/
            switch (operacion) {
            case REQ_AUTENTICAR:                
                if (autenticada()) {
                    msg_envio.data["respuesta"] = MENSAJE_AUTH_AUTENTICADO;
                } else {
                    // Se liga usuario y conexion, a partir de ahora 
                    // está conectado
                    Usuario* p_usuario;
                    if (servidor->autenticarUsuario(msg_recibido, 
                                                    &p_usuario,
                                                    respuesta) == 0) {
                        this->usuario= p_usuario;
                        this->usuario->setConexion(this);
                        this->usuario->getDetalles(contenido);
                        msg_envio.data["contenido"] = contenido;
                    }
                    
                    msg_envio.data["respuesta"] = respuesta;
                }
                break;
            case REQ_CREAR_USUARIO:
                servidor->crearUsuario(msg_recibido, respuesta);
                msg_envio.data["respuesta"] = respuesta;
                break;
            case REQ_DESCONECTAR:
                this->desconectar();
                responder_req = false;
                break;
            default:
                mensaje_de_sesion = true;
                break;
            }

            /* Mensajes validos para cualquier conexion autenticada */
            if (mensaje_de_sesion) {
                if (!this->autenticada()) {
                    msg_envio.data["respuesta"] = MENSAJE_NO_AUTENTICADO;
                } else {
                    if ((auth_error = servidor->autenticarMensaje(msg_recibido,
                                      * (this->usuario))) == 0) {
                        switch (operacion) {
                        case REQ_UNIR_A_PARTIDO: {
                           Log::loguear(MINFO, "Uniendo a partido...");
                           id = msg_recibido.data["idPartido"].asInt();
                           this->servidor->unirAPartido(usuario, id);
                           responder_req = false;
                        }
                        break;
                        case REQ_OUT_USER: {
                           Log::loguear(MINFO, "Quitando jugador de partido en sala...");
                           this->servidor->quitarDePartido(usuario);
                           responder_req = false;
                        }
                        break;
                        case REQ_PARTIDOS: {
                            Log::loguear(MINFO, "Devolviendo partidos...");
                            msg_envio.data["respuesta"] = 
                                        this->servidor->getPartidosDisponibles(
                                                            usuario->getNivel(),
                                                            contenido);
                            msg_envio.data["contenido"] = contenido;
                            contenido.clear();
                            // Le envio ademas informacion sobre los niveles 
                            // para los que puede crear partidos.
                            info_niveles_permitidos.clear();
                            this->servidor->getInformacionNiveles(
                                                    usuario->getNivel(),
                                                     info_niveles_permitidos);
                            msg_envio.data["contenido"]["niveles"] = 
                                                     info_niveles_permitidos;
                            msg_envio.data["not_type"] = NOT_PARTIDOS;
                        }
                        break;
                        case REQ_CREAR_PARTIDO: {
                            Log::loguear(MINFO, "Creando partido...");    

                            nivel = msg_recibido.data["nivel"].asInt();
                            cant_jugadores = 
                                    msg_recibido.data["cantJugadores"].asInt();
                            this->servidor->crearPartido(usuario, nivel, 
                                                         cant_jugadores);
                            responder_req = false;
                        }
                        break;
                        case REQ_COMANDO_PARTIDO: {
                            if (this->usuario->jugando()) {
                                responder_req = false;
                                // Despacho el mensaje al hilo del partido
                                // para que lo procese.
                                this->despacharEventoPartido(msg_recibido, 
                                                            this->usuario);
                            } else {
                                // El cliente mandó un comando sin estar 
                                // jugando ningun partido.
                                msg_envio.data["respuesta"]= ERROR_SIN_PARTIDO;
                                msg_envio.data["contenido"]= "";
                            }
                        }
                        break;
                        default:
                            mensaje_corrupto = true;
                            break;
                        }
                    } else {
                        msg_envio.data["respuesta"] = MENSAJE_MAL_FIRMADO;
                    }
                }
                if (mensaje_corrupto) {
                    Log::loguear(MWARN, "Se recibio mensaje corrupto...");
                    msg_envio.data["respuesta"] = MENSAJE_INVALIDO;
                }
            }
            if (responder_req)
                this->enviar(msg_envio);
        }
    }
    /* Se asegura de dejar los sockets cerrados y cerrar sesion antes 
       de salir */
    this->cerrarSesion();
    Log::loguear(MINFO, "Terminando conexión con usuario...");
    this->exit();
    this->finalizar();

    return NULL;
}

bool Conexion::autenticada() {
    return this->getUsuario() != NULL;
}

bool Conexion::estaConectada() {
    return this->conectado == true;
}

void Conexion::cerrarSesion() {
    if (autenticada()) {
        this->servidor->quitarDePartido(this->usuario);
        this->usuario->desconectar();
    }
}

int Conexion::enviar(Mensaje &mensaje) {
    /* Imprime por pantalla el request recibido */
    std::stringstream ss;
    if (MODO_DEBUG == 1 && !mensaje.data["not_type"].isNull()){
        int not_type= mensaje.data["not_type"].asInt();
        ss << "Enviando: " << Protocolo::notToString(not_type) << std::endl;
                      
        if (IMPRIMIR_NOTIFICACIONES == 1)
            ss << mensaje.aString() << std::endl;
        Log::loguear(MINFO, ss.str());
    }

    std::string mensaje_serial = mensaje.serializar();
    return socket->enviarConAvisoDeLong((char *) mensaje_serial.c_str(),
                                        (uint32_t) mensaje_serial.length());
}

void Conexion::desconectar() {
    this->socket->apagar();
    this->conectado = false;
}

void Conexion::despacharEventoPartido(Mensaje &mensaje, Usuario *usuario) {
    EventoPartido *evento=NULL;
    if (this->autenticada() && usuario->tienePartido() && 
                        usuario->getPartido()->ejecutando()) {
        evento = new EventoPartido();
        evento->setTipo((int) mensaje.data["tipo"].asInt());
        evento->argumentos = mensaje.data["argumentos"];
        evento->setJugador(usuario);
        this->usuario->getPartido()->procesar(evento);
    }
}
